home *** CD-ROM | disk | FTP | other *** search
- _GRAPHIC PROGRAMMING COLUMN_
- by Michael Abrash
-
- [LISTING ONE]
-
-
- ; Draws all pixels in the list of horizontal lines passed in, in
- ; mode X, the VGA's undocumented 320x240 256-color mode. Clips to
- ; the rectangle specified by (ClipMinX,ClipMinY),(ClipMaxX,ClipMaxY).
- ; Draws to the page specified by CurrentPageBase.
- ; C near-callable as:
- ; void DrawHorizontalLineList(struct HLineList * HLineListPtr,
- ; int Color);
- ;
- ; All assembly code tested with TASM 2.0 and MASM 5.0
-
- SCREEN_WIDTH equ 320
- SCREEN_SEGMENT equ 0a000h
- SC_INDEX equ 03c4h ;Sequence Controller Index
- MAP_MASK equ 2 ;Map Mask register index in SC
-
- HLine struc
- XStart dw ? ;X coordinate of leftmost pixel in line
- XEnd dw ? ;X coordinate of rightmost pixel in line
- HLine ends
-
- HLineList struc
- Lngth dw ? ;# of horizontal lines
- YStart dw ? ;Y coordinate of topmost line
- HLinePtr dw ? ;pointer to list of horz lines
- HLineList ends
-
- Parms struc
- dw 2 dup(?) ;return address & pushed BP
- HLineListPtr dw ? ;pointer to HLineList structure
- Color dw ? ;color with which to fill
- Parms ends
- .model small
- .data
- extrn _CurrentPageBase:word,_ClipMinX:word
- extrn _ClipMinY:word,_ClipMaxX:word,_ClipMaxY:word
- ; Plane masks for clipping left and right edges of rectangle.
- LeftClipPlaneMask db 00fh,00eh,00ch,008h
- RightClipPlaneMask db 001h,003h,007h,00fh
- .code
- align 2
- ToFillDone:
- jmp FillDone
- public _DrawHorizontalLineList
- align 2
- _DrawHorizontalLineList proc
- push bp ;preserve caller's stack frame
- mov bp,sp ;point to our stack frame
- push si ;preserve caller's register variables
- push di
- cld ;make string instructions inc pointers
- mov dx,SC_INDEX
- mov al,MAP_MASK
- out dx,al ;point SC Index to the Map Mask
- mov ax,SCREEN_SEGMENT
- mov es,ax ;point ES to display memory for REP STOS
- mov si,[bp+HLineListPtr] ;point to the line list
- mov bx,[si+HLinePtr] ;point to the XStart/XEnd descriptor
- ; for the first (top) horizontal line
- mov cx,[si+YStart] ;first scan line to draw
- mov si,[si+Lngth] ;# of scan lines to draw
- cmp si,0 ;are there any lines to draw?
- jle ToFillDone ;no, so we're done
- cmp cx,[_ClipMinY] ;clipped at top?
- jge MinYNotClipped ;no
- neg cx ;yes, discard however many lines are
- add cx,[_ClipMinY] ; clipped
- sub si,cx ;that many fewer lines to draw
- jle ToFillDone ;no lines left to draw
- shl cx,1 ;lines to skip*2
- shl cx,1 ;lines to skip*4
- add bx,cx ;advance through the line list
- mov cx,[_ClipMinY] ;start at the top clip line
- MinYNotClipped:
- mov dx,si
- add dx,cx ;bottom row to draw + 1
- cmp dx,[_ClipMaxY] ;clipped at bottom?
- jle MaxYNotClipped ;no
- sub dx,[_ClipMaxY] ;# of lines to clip off the bottom
- sub si,dx ;# of lines left to draw
- jle ToFillDone ;all lines are clipped
- MaxYNotClipped:
- mov ax,SCREEN_WIDTH/4 ;point to the start of the first
- mul cx ; scan line on which to draw
- add ax,[_CurrentPageBase] ;offset of first line
- mov dx,ax ;ES:DX points to first scan line to
- ; draw
- mov ah,byte ptr [bp+Color] ;color with which to fill
- FillLoop:
- push bx ;remember line list location
- push dx ;remember offset of start of line
- push si ;remember # of lines to draw
- mov di,[bx+XStart] ;left edge of fill on this line
- cmp di,[_ClipMinX] ;clipped to left edge?
- jge MinXNotClipped ;no
- mov di,[_ClipMinX] ;yes, clip to the left edge
- MinXNotClipped:
- mov si,di
- mov cx,[bx+XEnd] ;right edge of fill
- cmp cx,[_ClipMaxX] ;clipped to right edge?
- jl MaxXNotClipped ;no
- mov cx,[_ClipMaxX] ;yes, clip to the right edge
- dec cx
- MaxXNotClipped:
- cmp cx,di
- jl LineFillDone ;skip if negative width
- shr di,1 ;X/4 = offset of first rect pixel in scan
- shr di,1 ; line
- add di,dx ;offset of first rect pixel in display mem
- mov dx,si ;XStart
- and si,0003h ;look up left edge plane mask
- mov bh,LeftClipPlaneMask[si] ; to clip & put in BH
- mov si,cx
- and si,0003h ;look up right edge plane
- mov bl,RightClipPlaneMask[si] ; mask to clip & put in BL
- and dx,not 011b ;calculate # of addresses across rect
- sub cx,dx
- shr cx,1
- shr cx,1 ;# of addresses across rectangle to fill - 1
- jnz MasksSet ;there's more than one byte to draw
- and bh,bl ;there's only one byte, so combine the left
- ; and right edge clip masks
- MasksSet:
- mov dx,SC_INDEX+1 ;already points to the Map Mask reg
- FillRowsLoop:
- mov al,bh ;put left-edge clip mask in AL
- out dx,al ;set the left-edge plane (clip) mask
- mov al,ah ;put color in AL
- stosb ;draw the left edge
- dec cx ;count off left edge byte
- js FillLoopBottom ;that's the only byte
- jz DoRightEdge ;there are only two bytes
- mov al,00fh ;middle addresses are drawn 4 pixels at a pop
- out dx,al ;set the middle pixel mask to no clip
- mov al,ah ;put color in AL
- rep stosb ;draw the middle addresses four pixels apiece
- DoRightEdge:
- mov al,bl ;put right-edge clip mask in AL
- out dx,al ;set the right-edge plane (clip) mask
- mov al,ah ;put color in AL
- stosb ;draw the right edge
- FillLoopBottom:
- LineFillDone:
- pop si ;retrieve # of lines to draw
- pop dx ;retrieve offset of start of line
- pop bx ;retrieve line list location
- add dx,SCREEN_WIDTH/4 ;point to start of next line
- add bx,size HLine ;point to the next line descriptor
- dec si ;count down lines
- jnz FillLoop
- FillDone:
- pop di ;restore caller's register variables
- pop si
- pop bp ;restore caller's stack frame
- ret
- _DrawHorizontalLineList endp
- end
-
-
- [LISTING TWO]
-
-
- /* Matrix arithmetic functions.
- Tested with Borland C++ 2.0 in the small model */
-
- /* Matrix multiplies Xform by SourceVec, and stores the result in
- DestVec. Multiplies a 4x4 matrix times a 4x1 matrix; the result
- is a 4x1 matrix, as follows:
- -- -- -- -- -- --
- | | | 4 | | 4 |
- | 4x4 | X | x | = | x |
- | | | 1 | | 1 |
- -- -- -- -- -- -- */
- void XformVec(double Xform[4][4], double * SourceVec,
- double * DestVec)
- {
- int i,j;
-
- for (i=0; i<4; i++) {
- DestVec[i] = 0;
- for (j=0; j<4; j++)
- DestVec[i] += Xform[i][j] * SourceVec[j];
- }
- }
-
- /* Matrix multiplies SourceXform1 by SourceXform2 and stores the
- result in DestXform. Multiplies a 4x4 matrix times a 4x4 matrix;
- the result is a 4x4 matrix, as follows:
- -- -- -- -- -- --
- | | | | | |
- | 4x4 | X | 4x4 | = | 4x4 |
- | | | | | |
- -- -- -- -- -- -- */
- void ConcatXforms(double SourceXform1[4][4], double SourceXform2[4][4],
- double DestXform[4][4])
- {
- int i,j,k;
-
- for (i=0; i<4; i++) {
- for (j=0; j<4; j++) {
- DestXform[i][j] = 0;
- for (k=0; k<4; k++)
- DestXform[i][j] += SourceXform1[i][k] * SourceXform2[k][j];
- }
- }
- }
-
-
- [LISTING THREE]
-
-
- /* Transforms convex polygon Poly (which has PolyLength vertices),
- performing the transformation according to Xform (which generally
- represents a transformation from object space through world space
- to view space), then projects the transformed polygon onto the
- screen and draws it in color Color. Also updates the extent of the
- rectangle (EraseRect) that's used to erase the screen later.
- Tested with Borland C++ 2.0 in the small model */
- #include "polygon.h"
-
- void XformAndProjectPoly(double Xform[4][4], struct Point3 * Poly,
- int PolyLength, int Color)
- {
- int i;
- struct Point3 XformedPoly[MAX_POLY_LENGTH];
- struct Point ProjectedPoly[MAX_POLY_LENGTH];
- struct PointListHeader Polygon;
-
- /* Transform to view space, then project to the screen */
- for (i=0; i<PolyLength; i++) {
- /* Transform to view space */
- XformVec(Xform, (double *)&Poly[i], (double *)&XformedPoly[i]);
- /* Project the X & Y coordinates to the screen, rounding to the
- nearest integral coordinates. The Y coordinate is negated to
- flip from view space, where increasing Y is up, to screen
- space, where increasing Y is down. Add in half the screen
- width and height to center on the screen */
- ProjectedPoly[i].X = ((int) (XformedPoly[i].X/XformedPoly[i].Z *
- PROJECTION_RATIO*(SCREEN_WIDTH/2.0)+0.5))+SCREEN_WIDTH/2;
- ProjectedPoly[i].Y = ((int) (XformedPoly[i].Y/XformedPoly[i].Z *
- -1.0 * PROJECTION_RATIO * (SCREEN_WIDTH / 2.0) + 0.5)) +
- SCREEN_HEIGHT/2;
- /* Appropriately adjust the extent of the rectangle used to
- erase this page later */
- if (ProjectedPoly[i].X > EraseRect[NonDisplayedPage].Right)
- if (ProjectedPoly[i].X < SCREEN_WIDTH)
- EraseRect[NonDisplayedPage].Right = ProjectedPoly[i].X;
- else EraseRect[NonDisplayedPage].Right = SCREEN_WIDTH;
- if (ProjectedPoly[i].Y > EraseRect[NonDisplayedPage].Bottom)
- if (ProjectedPoly[i].Y < SCREEN_HEIGHT)
- EraseRect[NonDisplayedPage].Bottom = ProjectedPoly[i].Y;
- else EraseRect[NonDisplayedPage].Bottom = SCREEN_HEIGHT;
- if (ProjectedPoly[i].X < EraseRect[NonDisplayedPage].Left)
- if (ProjectedPoly[i].X > 0)
- EraseRect[NonDisplayedPage].Left = ProjectedPoly[i].X;
- else EraseRect[NonDisplayedPage].Left = 0;
- if (ProjectedPoly[i].Y < EraseRect[NonDisplayedPage].Top)
- if (ProjectedPoly[i].Y > 0)
- EraseRect[NonDisplayedPage].Top = ProjectedPoly[i].Y;
- else EraseRect[NonDisplayedPage].Top = 0;
- }
- /* Draw the polygon */
- DRAW_POLYGON(ProjectedPoly, PolyLength, Color, 0, 0);
- }
-
-
- [LISTING FOUR]
-
- /* POLYGON.H: Header file for polygon-filling code, also includes
- a number of useful items for 3D animation. */
-
- #define MAX_POLY_LENGTH 4 /* four vertices is the max per poly */
- #define SCREEN_WIDTH 320
- #define SCREEN_HEIGHT 240
- #define PAGE0_START_OFFSET 0
- #define PAGE1_START_OFFSET (((long)SCREEN_HEIGHT*SCREEN_WIDTH)/4)
- /* Ratio: distance from viewpoint to projection plane / width of
- projection plane. Defines the width of the field of view. Lower
- absolute values = wider fields of view; higher values = narrower */
- #define PROJECTION_RATIO -2.0 /* negative because visible Z
- coordinates are negative */
- /* Draws the polygon described by the point list PointList in color
- Color with all vertices offset by (X,Y) */
- #define DRAW_POLYGON(PointList,NumPoints,Color,X,Y) \
- Polygon.Length = NumPoints; \
- Polygon.PointPtr = PointList; \
- FillConvexPolygon(&Polygon, Color, X, Y);
-
- /* Describes a single 2D point */
- struct Point {
- int X; /* X coordinate */
- int Y; /* Y coordinate */
- };
- /* Describes a single 3D point in homogeneous coordinates */
- struct Point3 {
- double X; /* X coordinate */
- double Y; /* Y coordinate */
- double Z; /* Z coordinate */
- double W;
- };
- /* Describes a series of points (used to store a list of vertices that
- describe a polygon; each vertex is assumed to connect to the two
- adjacent vertices, and the last vertex is assumed to connect to the
- first) */
- struct PointListHeader {
- int Length; /* # of points */
- struct Point * PointPtr; /* pointer to list of points */
- };
-
- /* Describes the beginning and ending X coordinates of a single
- horizontal line */
- struct HLine {
- int XStart; /* X coordinate of leftmost pixel in line */
- int XEnd; /* X coordinate of rightmost pixel in line */
- };
-
- /* Describes a Length-long series of horizontal lines, all assumed to
- be on contiguous scan lines starting at YStart and proceeding
- downward (used to describe a scan-converted polygon to the
- low-level hardware-dependent drawing code) */
- struct HLineList {
- int Length; /* # of horizontal lines */
- int YStart; /* Y coordinate of topmost line */
- struct HLine * HLinePtr; /* pointer to list of horz lines */
- };
- struct Rect { int Left, Top, Right, Bottom; };
-
- extern void XformVec(double Xform[4][4], double * SourceVec,
- double * DestVec);
- extern void ConcatXforms(double SourceXform1[4][4],
- double SourceXform2[4][4], double DestXform[4][4]);
- extern void XformAndProjectPoly(double Xform[4][4],
- struct Point3 * Poly, int PolyLength, int Color);
- extern int FillConvexPolygon(struct PointListHeader *, int, int, int);
- extern void Set320x240Mode(void);
- extern void ShowPage(unsigned int StartOffset);
- extern void FillRectangleX(int StartX, int StartY, int EndX,
- int EndY, unsigned int PageBase, int Color);
- extern int DisplayedPage, NonDisplayedPage;
- extern struct Rect EraseRect[];
-
-
- [LISTING FIVE]
-
- /* Simple 3D drawing program to view a polygon as it rotates in
- mode X. View space is congruent with world space, with the
- viewpoint fixed at the origin (0,0,0) of world space, looking in
- the direction of increasingly negative Z. A right-handed
- coordinate system is used throughout.
- Tested with Borland C++ 2.0 in the small model */
- #include <conio.h>
- #include <stdio.h>
- #include <dos.h>
- #include <math.h>
- #include "polygon.h"
- void main(void);
-
- /* Base offset of page to which to draw */
- unsigned int CurrentPageBase = 0;
- /* Clip rectangle; clips to the screen */
- int ClipMinX=0, ClipMinY=0;
- int ClipMaxX=SCREEN_WIDTH, ClipMaxY=SCREEN_HEIGHT;
- /* Rectangle specifying extent to be erased in each page */
- struct Rect EraseRect[2] = { {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT},
- {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT} };
- /* Transformation from polygon's object space to world space.
- Initially set up to perform no rotation and to move the polygon
- into world space -140 units away from the origin down the Z axis.
- Given the viewing point, -140 down the Z axis means 140 units away
- straight ahead in the direction of view. The program dynamically
- changes the rotation and translation */
- static double PolyWorldXform[4][4] = {
- {1.0, 0.0, 0.0, 0.0},
- {0.0, 1.0, 0.0, 0.0},
- {0.0, 0.0, 1.0, -140.0},
- {0.0, 0.0, 0.0, 1.0} };
- /* Transformation from world space into view space. In this program,
- the view point is fixed at the origin of world space, looking down
- the Z axis in the direction of increasingly negative Z, so view
- space is identical to world space; this is the identity matrix */
- static double WorldViewXform[4][4] = {
- {1.0, 0.0, 0.0, 0.0},
- {0.0, 1.0, 0.0, 0.0},
- {0.0, 0.0, 1.0, 0.0},
- {0.0, 0.0, 0.0, 1.0}
- };
- static unsigned int PageStartOffsets[2] =
- {PAGE0_START_OFFSET,PAGE1_START_OFFSET};
- int DisplayedPage, NonDisplayedPage;
-
- void main() {
- int Done = 0;
- double WorkingXform[4][4];
- static struct Point3 TestPoly[] =
- {{-30,-15,0,1},{0,15,0,1},{10,-5,0,1}};
- #define TEST_POLY_LENGTH (sizeof(TestPoly)/sizeof(struct Point3))
- double Rotation = M_PI / 60.0; /* initial rotation = 3 degrees */
- union REGS regset;
-
- Set320x240Mode();
- ShowPage(PageStartOffsets[DisplayedPage = 0]);
- /* Keep rotating the polygon, drawing it to the undisplayed page,
- and flipping the page to show it */
- do {
- CurrentPageBase = /* select other page for drawing to */
- PageStartOffsets[NonDisplayedPage = DisplayedPage ^ 1];
- /* Modify the object space to world space transformation matrix
- for the current rotation around the Y axis */
- PolyWorldXform[0][0] = PolyWorldXform[2][2] = cos(Rotation);
- PolyWorldXform[2][0] = -(PolyWorldXform[0][2] = sin(Rotation));
- /* Concatenate the object-to-world and world-to-view
- transformations to make a transformation matrix that will
- convert vertices from object space to view space in a single
- operation */
- ConcatXforms(WorldViewXform, PolyWorldXform, WorkingXform);
- /* Clear the portion of the non-displayed page that was drawn
- to last time, then reset the erase extent */
- FillRectangleX(EraseRect[NonDisplayedPage].Left,
- EraseRect[NonDisplayedPage].Top,
- EraseRect[NonDisplayedPage].Right,
- EraseRect[NonDisplayedPage].Bottom, CurrentPageBase, 0);
- EraseRect[NonDisplayedPage].Left =
- EraseRect[NonDisplayedPage].Top = 0x7FFF;
- EraseRect[NonDisplayedPage].Right =
- EraseRect[NonDisplayedPage].Bottom = 0;
- /* Transform the polygon, project it on the screen, draw it */
- XformAndProjectPoly(WorkingXform, TestPoly, TEST_POLY_LENGTH,9);
- /* Flip to display the page into which we just drew */
- ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]);
- /* Rotate 6 degrees farther around the Y axis */
- if ((Rotation += (M_PI/30.0)) >= (M_PI*2)) Rotation -= M_PI*2;
- if (kbhit()) {
- switch (getch()) {
- case 0x1B: /* Esc to exit */
- Done = 1; break;
- case 'A': case 'a': /* away (-Z) */
- PolyWorldXform[2][3] -= 3.0; break;
- case 'T': /* towards (+Z). Don't allow to get too */
- case 't': /* close, so Z clipping isn't needed */
- if (PolyWorldXform[2][3] < -40.0)
- PolyWorldXform[2][3] += 3.0; break;
- case 0: /* extended code */
- switch (getch()) {
- case 0x4B: /* left (-X) */
- PolyWorldXform[0][3] -= 3.0; break;
- case 0x4D: /* right (+X) */
- PolyWorldXform[0][3] += 3.0; break;
- case 0x48: /* up (+Y) */
- PolyWorldXform[1][3] += 3.0; break;
- case 0x50: /* down (-Y) */
- PolyWorldXform[1][3] -= 3.0; break;
- default:
- break;
- }
- break;
- default: /* any other key to pause */
- getch(); break;
- }
- }
- } while (!Done);
- /* Return to text mode and exit */
- regset.x.ax = 0x0003; /* AL = 3 selects 80x25 text mode */
- int86(0x10, ®set, ®set);
- }
-